MVVMフレームワーク「Knockout.js」が超絶便利!!その概要と使いどころなどについて

2014年01月20日

カテゴリー:

私の中のJavaScriptの歴史で、Knockout.js(のっくあうと)との出会いはjQueryを知った時以来の衝撃でした。

ページの再読み込みではなくJavaScriptによるDOMのリフローを頻繁に行うようなWEBアプリケーションを作成する場合、アプリケーションの規模が大きくなるにつれHTMLマークアップとJavaScriptコードが随所で絡み合ってしまい、次第にコードのメンテナンスや機能拡張が困難になっていきます。そんな経験をしたことのある人は結構多いのではないでしょうか。

ここで紹介する「Knockout.js」はそんな問題を劇的に改善してくれるJavaScriptフレームワークです。


本来このブログではJavaScriptそのものについての理解を深めるというテーマのもと、この言語が持つ特徴やコーディングパターンなどをメインにエントリを書いてきました。
ですので、JavaScriptライブラリ/フレームワークやjQueryブラグイン等の使い方については触れない予定でしたが、最近私が実務で使い出したJavaScriptフレームワーク「knockout.js」が本当に便利で、もっと使いこなしたいと思うようになってきたので、自身の勉強も兼ねてしばらくはknockout.jsについてのエントリを書いていこうと思います。

Knockout.jsとは?

Knockout.jsを一言でいうと、「MVVM(Model-View-ViewModel)パターンをサポートするためのJavaScriptフレームワーク」です。それ自身が純粋なJavaScriptで作成されているため、Webサイト・Webアプリケーションに簡単に導入することができます。

MVVMパターンをサポートするJavaScriptフレームワークはKnockout.js以外にもいくつかあるようですが、当ブログではKnockout.jsに焦点を絞ってエントリを書いていきます。その他のフレームワークの概要やそれぞれの長所、短所等が知りたければ以下のサイトが参考になると思います。

AngularJS – 新・三大JavaScriptフレームワークの実践(Backbone.js Knockout.js Angular.js) – Qiita [キータ]

Knockout.jsを「JavaScriptライブラリ」と紹介している記事をたまに見かけますが、その性質上ライブラリというよりは「JavaScriptフレームワーク」の方が意味合いとしては正しいと思うので、本エントリではフレームワークとして紹介します。

Knockout.jsはフレームワークなので、jQueryのようなJavaScriptライブラリとは基本的に別物です。jQueryを置き換えるようなものではありませんし、もちろん共存させることも可能です。
ですが、これまでDOM操作の大部分をjQueryベースで実現していたようなケースでは、そのほぼ全てをKnockout.jsに置き換えることができます。そしてjQueryを使った場合よりも、HTML、JavaScriptの両者のコードを分離しやすくなるはずです。

そもそもMVVMパターンとは?

MVVMパターンについては既にたくさんの有益な情報がネット上にありますので、私がここで下手な解説をするよりはググってもらった方がよっぽど正しく理解できると思います。

Model-View-ViewModel(MVVM)パターン とはプレゼンテーション(Presentation)とドメイン(Domain)の分割を目的としたMVC系のパターンの1つで、アプリケーションのコードをModel、View、ViewModelという3つの責務に分割して記述します。ここでのプレゼンテーションとはユーザインタフェース(UI)を実装するプラットフォームに依存した部分、ドメインはプラットフォームに依存しない部分を指しています。Webアプリで言えば、プレゼンテーションはHTMLの都合が関係ある部分、ドメインはHTMLの都合が関係ない部分になります。

引用元:[Knockout]MVVMパターンでアプリケーションを構築する

・・・が、一応私なりの理解の仕方で「WEBアプリケーションの場合のMVVMパターン」について整理してみました。所々間違っているかもしれませんので、あくまでも参考程度でお願いします。

140304_mvvm_pattern
  1. システム全体をクライアントサイドとサーバサイドに分ける。
  2. MVVMはクライアントサイドのコードに適用されるパターンなのでサーバサイドについては関知しない。
  3. クライアントサイドとサーバサイドの連携には通常、AJAX(SOAP、RESTなど)が使われる。
  4. クライアントサイドのコードを、HTMLの都合が関係ある部分=プレゼンテーション(Presentation)とHTMLの都合が関係ない部分=ドメイン(Domain)とに分ける
  5. プレゼンテーションを、UIのテンプレートを定義するHTML/CSS=ビュー(View)と、UIの状態の保持、およびUIから呼び出されるメソッドを定義したJavaScript=ビューモデル(ViewModel)とに分ける

このことを、MVVM(Model-View-ViewModel)の各セクションに当てはめて整理すると、以下の様になります。

M(Model)
クライアントサイドのコードの内、HTMLの都合が関係ない部分(ドメイン)のロジックを実装するJavaScriptコード。
Webサービスの呼び出しや、共通して使われる汎用的なコード(ライブラリ)など。
V(View)
クライアントサイドのコードの内、UIのテンプレートを定義するHTML/CSS。
VM(ViewModel)
クライアントサイドのコードの内、UIの状態保持、および、UIから呼び出されるメソッドを定義したJavaScriptコード

さて、ここでポイントとなるのは、ViewとViewModel間との連携です。
ViewModelは「UIの状態の保持」と書きましたが、このViewModelの中のUIの状態は、例えば「状態保持オブジェクト」のようなものを作成し、そこにUIの状態を書き込んでいくことになると思います。
だとすると、このUIの状態(=状態保持オブジェクトのプロパティの値)をどのようにしてViewに反映させるのでしょうか?

実は、MVVMパターンではViewとViewModel間との連携は「データバインド」と呼ばれる仕組みを通じて行うことが前提になっています。データバインドは通常、Knockout.js等のフレームワーク側が行うものなので、サイト制作者がこの仕組みを構築する必要はありません。

フレームワーク側はViewModelが持っているUIの状態(状態保持オブジェクト)の内容を監視し、このオブジェクトの内容をView側(つまりHTML)に反映させたり、逆にView側の変化をオブジェクトに反映させたりすることができます。 つまり、データバインドはViewの内容とViewModelの「状態保持オブジェクト」の紐づけをしてくれるのです。また、一度紐づけが行われたら、以降はView/ViewModelのどちらか一方の状態が変化した場合も、リアルタイムでもう一方の状態も更新されるようになります。

このことは次の様なメリットをもたらします。

  • DOM操作のためのJavaScriptコードがほとんど不要になる
  • UIへの入出力のためにHTML要素へのid設定が不要になる
  • DOM構造を意識せずにModelを作成できる

MVVMパターンを導入することによって、UIデザイナ/マークアップエンジニアとJavaScriptプログラマとの分業がしやすくなるというわけです。

Knockout.jsを使うとどのようにコードが変わるか?

例えばユーザのアクションに応じてUIを変化させる必要があるような場合、どのような方法で実現しますか?

jQueryのようなライブラリを使って、イベントハンドラ(関数)を作成し、ハンドラの中にDOMを操作するためのコードをゴリゴリと書いていく、そんなやり方がスタンダードだったのではないでしょうか。(私がそうでした。)

以下は、ボタンをクリックするとリスト(<ul>要素)の中身が増えていくというシンプルな機能をKnockout.jsを使わずにjQueryで書いた例です。

<ul></ul>
<button>要素を追加</button>
$('button').click(function () {
    var $ul = $('ul'),
        $li = $('<li>fuga</li>');

    $ul.append($li);
});

上記と同じ機能をjQueryを使わずに、今度はKnockout.jsを使って書いた例が下記になります。

<ul data-bind="foreach: items">
  <li data-bind="text: $data"></li>
</ul>
<button data-bind="click: addItem">要素を追加</button>
    //HTMLの状態を保持するオブジェクト
var viewModel = {
        //リストの中身に相当するデータ配列
        items: ko.observableArray([]),

        //HTML側から呼び出されるメソッド
        addItem: function () {
            this.items.push('fuga');
        }
    };

//状態保持オブジェクトとHTMLテンプレートを紐づけする
ko.applyBindings(viewModel);

Knockout.jsの良さをまるで伝えられていないサンプルコード(しかも、この程度の機能の場合Knockout.jsの方がコード量が多くなってしまっているような気がします。。)ですが、ここで注目してもらいたいのは、Knockoutを使ったコードの方では、JavaScriptコード側で一切のDOMの操作をしていないということです。

Knockout.jsではHTML側で「テンプレート」を定義し、「テンプレートの状態」はJavaScript側に持たせます。テンプレートとテンプレートの状態は、先ほどのデータバインドの仕組みによりお互いが紐づけられます。DOMを変更する場合は、JavaScript側でDOMを直接操作するのではなく、テンプレートの状態(サンプルコードのitemsの値)を更新することにより自動的にHTML側のレンプレートに反映されます。

上記のKnockout.jsサンプルの方では、HTMLコードとJavaScriptコードの両者に見慣れない記述がありますが、これはKnockout.js固有のコードです。本エントリではまずはKnockout.jsの概要に絞って解説していますので、これらの意味については別エントリで紹介します。
とはいえ既にKnockout.jsについてはドキュメントも公開されていますので、興味があれば下記サイトをチェックしてみて下さい。

▼Knockout(本家サイト)
http://knockoutjs.com/

▼ドキュメントKnockout.js(非公式翻訳ドキュメント)
http://kojs.sukobuto.com/

メリット・デメリット

メリット1:マークアップの変更に強い

上のサンプルコードで、例えば追加するli要素にclass属性等の何らかの属性を付けたい場合、前者(jQuery版)の場合だとJavaScriptコードを修正しなければなりません。

これに対し、後者(Knockout.js版)の方では、マークアップの変更はHTMLテンプレート側で行えば良いだけですので、JavaScriptの修正は必要ありません。

HTML側からすればJavaScriptのコードの内容は関係ありません。HTML側とJavaScript側の唯一の接点であるViewModelの構造さえ共有できていれば、両者は互いに自由に修正を行うことが可能になるのです。

例えばデザイナーがHTML/CSSのマークアップを行い、JavaScriptコーディングをプログラマが行うといケースはわりとよくあるのではないかと思います。

前者(jQuery版)の方では、デザイナーはUIの見た目を修正するために一部のマークアップを変更したいだけなのに、対象のHTMLマークアップがJavaScript側にあるため、その都度プログラマに修正依頼をしなければなりません。これに対し、Knockout.jsを使用しているケースでは、あくまでもHTML側のみでテンプレートが定義されているので、デザイナーは自由にマークアップを変更することが可能になります。

メリット2:冗長なid、class設定が不要になる

jQueryを使ってDOMの操作を行う場合、操作対象のHTML要素を特定する必要があります。したがって、操作対象のHTML要素にJavaScriptからアクセスするためのidやclass属性を付加する必要があります(必ずしも付加する必要はありませんが、jQueryで要素を特定する際のセレクタが冗長になります)。
Knockout.jsではJavaScript側でDOMの操作を行いません。したがってHTML要素にidやclass属性を付加する必要がありません。

メリット3:HTMLとJavaScriptの分離

Knockout.jsではHTML側のコードにJavaScriptコードは入りません。Knockout.jsを使うためのバインディングと呼ばれる記述は必要ですが、data-bind属性を使ったHTMLレベルでの記述です。また、JavaScript側のコードにはDOM操作のためのコードは入りません。

これらのことは必然的に

  • HTMLとJavaScriptの分離
  • JavaScript肥大化の防止
  • 役割分担の明確化
  • メンテナンス性の向上

といったメリットをもたらします。

このようにKnockout.jsを使うとさまざまなメリットを享受することができますが、唯一のデメリット(というべきかどうかは分かりませんが)は、HTMLマークアップ内にKnockout.js固有の「バインディング」を記述する必要があるということです。そして、時には複雑なバインディングを記述する必要があるかもしれません。

これはMVVMパターンを使う場合の前提であり当たり前のことなのですが、せっかくHTMLとJavaScriptを分離できたとしても、HTML側に複雑なバインディングを書かなくてはならないようなケースではそのメリットも半減してしまうかもしれません。(多くの場合は設計を見直すことで、よりシンプルなバインディングに書き換えられるとは思いますが。)

ただ、バインディング自体はそこまで難しいものではないと思いますし、暗記する必要も無いでしょう。(私自身バインディングはほとんど暗記していません)
必要に応じて上で紹介しているようなサイトを見ながらコーディングしていけるはずです。

ブラウザサポート

Knockout.jsのブラウザサポート状況はKnockout : Browser supportに書かれています。英文ですので一応日本語に訳してまとめておいたのですが、英語力がゼロに等しい私としては是非原文を参考にしていただきたいと思います。

Mozilla Firefox 2.0以上
Google Chrome 5.0以上
Microsoft Internet Explorer 6以上
Safari

以下環境にて動作確認済み

  • Windows Safari 5.0
  • Mac OS X Safari 3.1.2
  • iPhone Safari for iOS 4-7
Opera 10以上
Opera Mini 対応(バージョン記載なし)
Google Android OS browser 対応(バージョン記載なし)

使いどころ

Knockout.jsは「MVVM(Model-View-ViewModel)パターンをサポートするためのJavaScriptフレームワーク」ですので、MVVMパターンを適用できるようなWEBアプリケーションを構築する際に利用すべきでしょう。

サーバサイドでDOMの構築を完了させ、ユーザーアクションの都度ページの再読み込みを行うような通常のWEBサイトを構築するのであれば、Knockout.jsの必要性はほとんどないと思います。また、クライアント側でDOMの振る舞いをちょこっと変える程度であれば、jQueryだけで十分です。

これに対し、AJAX通信で取得したJSONデータの内容に応じて頻繁にDOMの書き換えをおこなうようなリッチWEBアプリケーション(このようなWEBアプリケーションのことを「シングルページアプリケーション(SPA)」と言ったりします)を構築する場合にKnockout.jsは絶大な力を発揮します。

例えば、GmailのようなWEBメールクライアントだったり、Twitterクライアントツールの様な物でしょうか。(実際にこれらのツールがKnockout.jsの様なフレームワークを使用しているかどうかは分かりませんが)
企業サイトやブランドサイトの様なものよりは、どちらかというとユーザーがツールとして使えるようなWEBアプリ向けのフレームワークだと思います。

個人的には、このようなWEBアプリケーションはサービスの提供手段としては、今後より一般的になって来るのではないかと思います。ゆえに、Knockout.jsの様なJavaScriptフレームワークは今後ますます注目されるのではないかと感じています。


今回はKnockout.jsの概要についてまとめましたが、次回以降はもう少し掘り下げた内容を書いていきます。
皆さんも機会があれば是非挑戦してみてくださいね。